package com.ds.infrastructure.audit.client.handler;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ds.infrastructure.audit.client.annotation.KbaAudit;
import com.ds.infrastructure.audit.client.config.AuditConfig;
import com.ds.infrastructure.audit.client.constant.KbaClientConstant;
import com.ds.infrastructure.audit.client.factory.KbaTrackFactory;
import com.ds.infrastructure.audit.client.factory.ParserFactory;
import com.ds.infrastructure.audit.client.track.KbaTrack;
import com.ds.infrastructure.audit.common.entity.KbaBehavior;
import com.ds.infrastructure.audit.common.util.StringUtil;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 作用：
 *
 * @author WeiShaoying
 * @date 2020/7/27
 */
@Order(Ordered.HIGHEST_PRECEDENCE + 5)
@Aspect
@Slf4j
//@EnableConfigurationProperties({AuditConfig.class})//必须注释
@SuppressWarnings("all")
public class KbaTrackHandler {

    private static final String PV_RETURN = KbaClientConstant.PROTOCOL_VARIABLES_PREFIX + KbaClientConstant.PROTOCOL_VARIABLES_RETURN + KbaClientConstant.PROTOCOL_VARIABLES_SUFFIX;
    private static final String REGEX = "(\\$\\{[^}]+\\})";
    private static Pattern PATTERN = Pattern.compile(REGEX);
    private static Parser PARSER = ParserFactory.getParser();

    private final AuditConfig config;

    private KbaTrack client;

    public KbaTrackHandler(AuditConfig config) {
        this.config = config;
    }

    @PostConstruct
    private void init() {
        log.info("KbaTrack Config ={}", config.toString());
        client = KbaTrackFactory.getClient(config.getBrokers(), config.getAppName(), config.getAppKey());
    }

    /**
     * Pointcut
     */
    @Pointcut("@annotation(com.ds.infrastructure.audit.client.annotation.KbaAudit)")
    public void tracker() {
    }

    /**
     * Track Handler
     *
     * @param joinPoint 切入点
     * @return
     */
    @Around(value = "tracker()")
    public Object doTrack(ProceedingJoinPoint joinPoint) {
        log.info("===={}", joinPoint.getSignature().toShortString());
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String[] parameterNames = signature.getParameterNames();
        Object[] args = joinPoint.getArgs();
        if (log.isDebugEnabled()) {
            Arrays.asList(parameterNames).forEach(System.out::println);
            Arrays.asList(args).forEach(System.out::println);
        }
        KbaAudit annotation = signature.getMethod().getAnnotation(KbaAudit.class);
        Map<String, Object> map = getNameValueMap(joinPoint);
        //Map<String, Class> typeMap = getTypeMap(signature, parameterNames);
        Object result = null;
        try {
            result = joinPoint.proceed();
        } catch (Throwable throwable) {
            log.error(throwable.getMessage(), throwable);
        }
        KbaBehavior behavior = KbaBehavior.builder().build();
        try {
            /*String appName = annotation.appName();
            if (StringUtils.isBlank(appName)) {
                appName = config.getAppName();
            }*/
            behavior.setGroupId(process(map, result, annotation.groupId()));
            behavior.setShopId(process(map, result, annotation.shopId()));
            behavior.setBizName(process(map, result, annotation.bizName()));
            behavior.setBehaviorId(process(map, result, annotation.behaviorId()));
            behavior.setAction(process(map, result, annotation.action()));
            behavior.setActionDetail(process(map, result, annotation.actionDetail()));
            behavior.setPriority(annotation.priority());
            behavior.setUserId(process(map, result, annotation.userId()));
            behavior.setCreateTime(System.currentTimeMillis());
            behavior.setExtra(processMap(map, result, annotation.extra()));
            if (log.isDebugEnabled()) {
                log.info("上报:{}", behavior.toString());
            }
            client.report(behavior);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * Process Map
     *
     * @param map
     * @param result
     * @param extra
     * @return
     */
    private Map<String, String> processMap(Map<String, Object> map, Object result, String extra) {
        if (StringUtils.isEmpty(extra)) {
            return Collections.emptyMap();
        }
        JSONObject jsonObject = null;
        try {
            jsonObject = JSON.parseObject(extra);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        if (jsonObject == null || jsonObject.size() < 1) {
            return Collections.emptyMap();
        }
        Map<String, Object> innerMap = jsonObject.getInnerMap();
        //Map<String, String> retMap = Maps.newLinkedHashMapWithExpectedSize(innerMap.size());//NoSuchMethodError
        Map<String, String> retMap = new LinkedHashMap<>(innerMap.size());
        for (Map.Entry<String, Object> entry : innerMap.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            retMap.put(key, process(map, result, StringUtil.getString(value)));
        }
        return retMap;
    }

    /**
     * Process and compute
     *
     * @param map
     * @param result
     * @param parameter
     * @return
     */
    private String process(Map<String, Object> map, Object result, String parameter) {
        if (StringUtils.isEmpty(parameter) || !parameter.contains(KbaClientConstant.PROTOCOL_VARIABLES_PREFIX) || !parameter.contains(KbaClientConstant.PROTOCOL_VARIABLES_SUFFIX)) {
            return parameter;
        }
        parameter = parameter.trim();
        Matcher matcher = PATTERN.matcher(parameter);
        while (matcher.find()) {
            String varProperty = matcher.group();
            String pureProperty = varProperty.substring(2, varProperty.length() - 1);
            if (pureProperty.contains(".")) {
                String rootParam = pureProperty.split("\\.")[0];
                String expression = pureProperty.substring(rootParam.length() + 1);
                Object dynaVal;
                if (KbaClientConstant.PROTOCOL_VARIABLES_RETURN.equals(rootParam)) {
                    dynaVal = PARSER.parse(result, expression);
                } else {
                    dynaVal = PARSER.parse(map.get(rootParam), expression);
                }
                //All:正则
                parameter = parameter.replace(varProperty, StringUtil.getStringSupportTime(dynaVal));
            } else {
                if (KbaClientConstant.PROTOCOL_VARIABLES_RETURN.equals(pureProperty)) {
                    parameter = parameter.replace(varProperty, StringUtil.getStringSupportTime(result));
                } else {
                    parameter = parameter.replace(varProperty, StringUtil.getStringSupportTime(map.get(pureProperty)));
                }
            }
        }
        return parameter;
    }

    /**
     * 获得参数对应Map
     *
     * @param joinPoint
     * @return
     */
    private Map<String, Object> getNameValueMap(ProceedingJoinPoint joinPoint) {
        String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
        if (paramNames == null || paramNames.length < 1) {
            return Collections.emptyMap();
        }
        Object[] paramValues = joinPoint.getArgs();
        Map<String, Object> map = Maps.newHashMapWithExpectedSize(paramNames.length);
        for (int i = 0; i < paramNames.length; i++) {
            map.put(paramNames[i], paramValues[i]);
        }
        return map;
    }

    /**
     * @param signature
     * @param parameterNames
     * @return
     */
    private Map<String, Class> getTypeMap(MethodSignature signature, String[] parameterNames) {
        Class[] parameterTypes = signature.getParameterTypes();
        Map<String, Class> typeMap = Maps.newHashMap();
        for (int i = 0; i < parameterNames.length; i++) {
            typeMap.put(parameterNames[i], parameterTypes[i]);
        }
        typeMap.put(PV_RETURN, signature.getReturnType());
        return typeMap;
    }

}
